#include "shader.h"
#include "c_defines.h"
#include <cstdarg>
#include <cstring>
#include <fstream>
#include <memory>
#include <stdexcept>

using namespace std;

CShader::~CShader()
{
    if (m_Id != 0)
        glDeleteProgram(m_Id);

    m_mapUniforms.clear();
}

bool CShader::LoadShader(const char *vertexProg, const char *fragmentProg, ...)
{
    GLuint vertexShader = 0;
    GLuint fragmentShader = 0;
    GLint  testVal = -1;

    try
    {
        // 1. 创建着色器程序
        vertexShader = glCreateShader(GL_VERTEX_SHADER);
        fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);

        // 2. 加载着色器源码
        if (!LoadShaderSourceCode(vertexProg, vertexShader))
            throw runtime_error("load vertex code failed!");

        // 3. 编译着色器
        glCompileShader(vertexShader);
        // 4. 检查着色器编译错误
        glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &testVal);
        if (GL_FALSE == testVal)
        {
            GLchar infoLog[1024];
            glGetShaderInfoLog(vertexShader, 1024, nullptr, infoLog);
            throw runtime_error("Compile vertex failed!\n" + string(infoLog));
        }

        if (!LoadShaderSourceCode(fragmentProg, fragmentShader))
            throw runtime_error("load fragment code failed!");

        glCompileShader(fragmentShader);
        glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &testVal);
        if (GL_FALSE == testVal)
        {
            GLchar infoLog[1024];
            glGetShaderInfoLog(fragmentShader, 1024, nullptr, infoLog);
            throw runtime_error("Compile fragment failed!\n" + string(infoLog));
        }

        // 5. 连接着色器
        m_Id = glCreateProgram();
        glAttachShader(m_Id, vertexShader);
        glAttachShader(m_Id, fragmentShader);

        // 6. 绑定参数
        va_list attributes;
        va_start(attributes, fragmentProg);
        char *nextArg = nullptr;
        auto  argCount = va_arg(attributes, int);
        for (int i = 0; i < argCount; ++i)
        {
            int index = va_arg(attributes, int);
            nextArg = va_arg(attributes, char *);
            glBindAttribLocation(m_Id, index, nextArg);
        }
        va_end(attributes);

        // 7. 尝试连接,确认连接成功
        glLinkProgram(m_Id);

        glDeleteProgram(vertexShader);
        glDeleteProgram(fragmentShader);

        glGetProgramiv(m_Id, GL_LINK_STATUS, &testVal);
        if (GL_FALSE == testVal)
        {
            GLchar infoLog[1024];
            glGetProgramInfoLog(m_Id, 1024, nullptr, infoLog);
            glDeleteProgram(m_Id);
            vertexShader = 0;
            fragmentShader = 0;
            throw runtime_error("Link shader failed!\n" + string(infoLog));
        }
        return true;
    }
    catch (runtime_error &e)
    {
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);
        m_Id = 0;

        ERROR("%s", e.what());
        return false;
    }
}

bool CShader::LoadShaderSourceCode(const char *file, GLuint shader)
{
    ifstream inFile(file, ios::in | ios::binary);

    if (inFile.is_open())
    {
        inFile.seekg(0, inFile.end);
        size_t fileLength = inFile.tellg();
        inFile.seekg(0, inFile.beg);

        if (fileLength >= MaxShaderLength)
        {
            ERROR("shader file is too large!");
            inFile.close();
            return false;
        }

        bzero((void *)m_codeString, MaxShaderLength);
        size_t length = inFile.readsome(reinterpret_cast<char *>(m_codeString), fileLength);
        inFile.close();

        GLchar *temp = reinterpret_cast<GLchar *>(m_codeString);
        glShaderSource(shader, 1, &temp, nullptr);

        return true;
    }
    else
    {
        ERROR("open %s failed!", file);
        return false;
    }
}

/**
 * @date 2023/1/13
 * @bug 使用堆内存保存着色器代码字符串导致内存泄漏
 * @details
 * 调用glShaderSource之后,字符串并没有拷贝到opengl内存中,因此该内存区域在调用glCompileShader之前需要可以进行有效访问
 */